package org.openapi.service.impl;

import lombok.extern.slf4j.Slf4j;
import org.openapi.common.ApiConfig;
import org.openapi.consts.ApiConst;
import org.openapi.consts.CacheKeys;
import org.openapi.consts.LogType;
import org.openapi.domain.*;
import org.openapi.service.IApiLoaderService;
import org.openapi.service.IDbService;
import org.openapi.service.IOpenApiService;
import org.openapi.utils.SqlUtil;
import org.openapi.utils.StrUtil;
import org.openapi.vo.TableData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.stereotype.Service;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

/**
 * 自动加载启动资源
 */
@Service
@Slf4j
public class ApiLoaderServiceImpl implements IApiLoaderService {
    @Autowired
    private IDbService dbService;
    @Autowired
    private IOpenApiService openApiService;
    @Autowired
    private DataSource dataSource;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private NamedParameterJdbcTemplate jdbcTemplate;

    /**
     * 查询缓存
     * @param cacheKey
     * @param <T>
     * @return
     */
    private <T> T getCacheObject(String cacheKey){
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(cacheKey);
    }

    /**
     * 设置缓存
     * @param cacheKey
     * @param cacheVal
     * @param <T>
     */
    private <T> void setCacheObject(String cacheKey, T cacheVal){
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        operation.set(cacheKey, cacheVal);
    }

    /**
     * 加载模型
     */
    @Override
    public int loadModel(){
        log.debug("开始加载数据模型");
        Set<String> keys = redisTemplate.keys(CacheKeys.CACHE_MODEL+"**");
        List<Map<String,Object>> list = dbService.query("SELECT * FROM api_model");
        for(Map row:list){
            ApiModel model = ApiModel.fromMap(row);
            loadModel(model);
            keys.remove(CacheKeys.CACHE_MODEL + model.getCode());
        }
        if(!keys.isEmpty()){
            log.info("删除数据模型: {}", keys);
            redisTemplate.delete(keys);
        }
        return list.size();
    }

    /**
     * 加载模型的字段和权限
     * @param model
     */
    @Override
    public void loadModel(ApiModel model){
        log.debug("加载模型 {} : {}",  model.getCode(), model.getName());
        try {
            Connection connection = dataSource.getConnection();
            DatabaseMetaData dm = connection.getMetaData();
            ResultSet rs = dm.getColumns(connection.getCatalog(), connection.getSchema(), model.getCode(), null);
            while(rs.next()){
                ApiField field = new ApiField();
                field.setModelId(model.getId());
                field.setCode(rs.getString("COLUMN_NAME"));
                field.setName(rs.getString("REMARKS"));
                field.setType(rs.getInt("DATA_TYPE"));
                field.setMaxLength(rs.getInt("COLUMN_SIZE"));
                field.setMinLength(0);
                field.setScale(rs.getInt("DECIMAL_DIGITS"));
                field.setNullable(rs.getInt("NULLABLE") == 1);
                field.setDefaults(rs.getString("COLUMN_DEF"));
                model.addField(field);
                log.debug(rs.getString("TABLE_NAME")+" - "+rs.getString("COLUMN_NAME")+" - "+rs.getString("COLUMN_NAME")+ " - " +rs.getInt("NULLABLE"));
            }

            //同步字段
            List<Map<String,Object>> list = dbService.query("SELECT * FROM api_field WHERE model_id = "+model.getId());
            for(Map row:list){
                ApiField field = model.getField(row.get("code").toString());
                if(field == null){
                    dbService.exec("DELETE FROM api_field WHERE id = " + row.get("id").toString());
                }else{
                    field.fromMap(row);
                }
            }
            for(ApiField f:model.getFields()){
                if(f.getId() != null){
                    continue;
                }

                TableData table = new TableData();
                table.setTable("api_field");
                table.setIgnore(true);
                table.addData("table_code", model.getCode());
                table.addData("model_id", f.getModelId());
                table.addData("code", f.getCode());
                table.addData("name", f.getName());
                table.addData("type", f.getType());
                table.addData("max_length", f.getMaxLength() == null ? 0 : f.getMaxLength());
                table.addData("min_length", f.getMinLength());
                table.addData("scale", f.getScale());
                table.addData("nullable", f.getNullable() ? "Y" : "N");
                table.addData("rules", "");
                table.addData("defaults", f.getDefaults());

                GeneratedKeyHolder holder = new GeneratedKeyHolder();
                String sql = SqlUtil.insertSql(table.getTable(), table.getData());
                jdbcTemplate.update(sql, new MapSqlParameterSource(table.getData()), holder);
                f.setId(holder.getKey().longValue());
            }

            //权限
            list = dbService.query("SELECT * FROM api_access WHERE model_id = "+model.getId());
            for(Map row:list){
                model.addAccess(ApiAccess.fromMap(row));
            }

            //自动生成导入导出模型
            if(StrUtil.isNotEmpty(model.getExcels()) && ApiConst.AUTO_EXCELS.equalsIgnoreCase(model.getExcels())){
                StringBuilder sb = new StringBuilder();
                sb.append("{\n  \"").append(model.getCode()).append("\":{\n");
                for(ApiField f:model.getFields()){
                    sb.append("\t\"").append(f.getCode()).append("\":\"").append(f.getName()).append("\",\n");
                }
                sb.setLength(sb.length()-2);
                sb.append("\n  },\n\t\"@dict\":{},\n\t\"@ds\":{},\n\t\"@excel\":{}\n}");
                model.setExcels(sb.toString());
                //保存入库
                TableData table = new TableData();
                table.setTable("api_model");
                table.setIgnore(true);
                table.addData("excels", model.getExcels());
                table.addData("id", model.getId());
                dbService.updateData(table);
            }

            setCacheObject(CacheKeys.CACHE_MODEL + model.getCode(), model);
            connection.close();
        }catch (SQLException e){
            e.printStackTrace();
        }
    }

    /**
     * 加载事件
     */
    @Override
    public int loadEvent(){
        log.debug("开始加载模型事件");
        Set<String> keys = redisTemplate.keys(CacheKeys.CACHE_EVENT+"**");
        List<Map<String,Object>> list = dbService.query("SELECT * FROM api_event");
        for(Map row:list){
            ApiEvent event = ApiEvent.fromMap(row);
            String key = CacheKeys.CACHE_EVENT+event.getModelId()+"_"+event.getAction();
            setCacheObject(key, event);
            keys.remove(key);
        }
        if(!keys.isEmpty()){
            log.info("删除模型事件: {}", keys);
            redisTemplate.delete(keys);
        }
        return list.size();
    }

    /**
     * 加载内置SQL
     */
    @Override
    public int loadSql(){
        log.debug("开始加载内置SQL脚本");
        Set<String> keys = redisTemplate.keys(CacheKeys.CACHE_SQL+"**");
        List<Map<String,Object>> list = dbService.query("SELECT * FROM api_sql");
        for(Map row:list){
            ApiSql sql = ApiSql.fromMap(row);
            String key = CacheKeys.CACHE_SQL+sql.getCode();
            setCacheObject(key, sql);
            keys.remove(key);
        }
        if(!keys.isEmpty()){
            log.info("删除内置SQL脚本: {}", keys);
            redisTemplate.delete(keys);
        }
        return list.size();
    }

    /**
     * 加载数据交换接口
     */
    @Override
    public int loadExchange(){
        log.debug("开始加载数据交换接口");
        Set<String> keys = redisTemplate.keys(CacheKeys.CACHE_EXCHANGE+"**");
        List<Map<String,Object>> list = dbService.query("SELECT * FROM api_exchange");
        for(Map row:list){
            ApiExchange exchange = ApiExchange.fromMap(row);
            String key = CacheKeys.CACHE_EXCHANGE + (exchange.getUri().startsWith("/") ? "" : "/") + exchange.getUri();
            setCacheObject(key, exchange);
            keys.remove(key);
        }
        if(!keys.isEmpty()){
            log.info("删除数据交换接口: {}", keys);
            redisTemplate.delete(keys);
        }
        return list.size();
    }

    /**
     * 判断数据库类型
     */
    @Override
    public void decideDb(){
        try {
            Connection connection = dataSource.getConnection();
            DatabaseMetaData dm = connection.getMetaData();
            log.debug("当前数据库类型: {}, 版本: {}", dm.getDatabaseProductName(), dm.getDatabaseProductVersion());
            String dpn = dm.getDatabaseProductName().toUpperCase();
            if(dpn.contains("MYSQL")){
                ApiConfig.sqlType = SqlUtil.DB_MYSQL;
            }else if(dpn.contains("ORACLE")){
                ApiConfig.sqlType = SqlUtil.DB_ORACLE;
            }else if(dpn.contains("SQL SERVER")){
                ApiConfig.sqlType = SqlUtil.DB_SQL_SERVER;
            }else if(dpn.contains("POSTGRESQL")){
                ApiConfig.sqlType = SqlUtil.DB_POSTGRESQL;
            }else {
                ApiConfig.sqlType = SqlUtil.DB_ANSI;
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 查询模型
     * @param table
     * @return
     */
    @Override
    public ApiModel getModel(String table){
        ApiModel model = getCacheObject(CacheKeys.CACHE_MODEL+table);
        if(model == null) {
            dbService.log(null, LogType.ERROR.getCode(), null, "模型未定义 @" + table);
        }
        return model;
    }

    /**
     * 查询模型事件
     * @param modelId
     * @param action
     * @return
     */
    @Override
    public ApiEvent getEvent(Long modelId, String action){
        return getCacheObject(CacheKeys.CACHE_EVENT+modelId+"_"+action);
    }

    /**
     * 查询内置脚本
     * @param code
     * @return
     */
    @Override
    public ApiSql getSql(String code){
        ApiSql sql =  getCacheObject(CacheKeys.CACHE_SQL+code);
        if(sql == null) {
            dbService.log(null, LogType.ERROR.getCode(), null, "脚本未定义 @" + code);
        }
        return sql;
    }

    /**
     * 查询数据交换接口
     * @param uri
     * @return
     */
    @Override
    public ApiExchange getExchange(String uri){
        return getCacheObject(CacheKeys.CACHE_EXCHANGE+uri);
    }

    /**
     * 验证当前用户对指定模型是否有对应的权限
     * @param model
     * @param mode
     * @return
     */
    @Override
    public boolean checkAccess(ApiModel model, String mode){
        //超管不限权限
        if(openApiService.isAdmin()){
            return true;
        }

        //未配置，默认公开
        if(model.getAccessMap() == null || model.getAccessMap().isEmpty()){
            return true;
        }

        ApiAccess access = null;
        for(String role:openApiService.getLoginUser().getRoles()){
            access = model.getAccessMap().get(role);
            if(access != null){
                break;
            }
        }
        if(access == null) {
            return false;
        }else {
            if(ApiAccess.READ_MODE.equalsIgnoreCase(mode)){
                return access.getRead();
            }else if(ApiAccess.WRITE_MODE.equalsIgnoreCase(mode)){
                return access.getWrite();
            }else if(ApiAccess.REMOVE_MODE.equalsIgnoreCase(mode)){
                return access.getRemove();
            }else{
                return false;
            }
        }
    }
}
